﻿using System;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Threading;

namespace Rio.Framework.Pages
{
    /// <summary>
    /// A control which renders a reflection of another FrameworkElement
    /// </summary>
    public partial class ReflectionControl
    {
        private readonly DispatcherTimer _dispatcherTimer;

        private double _time;


        #region ReflectedElement

        /// <summary>
        /// ReflectedElement Dependency Property
        /// </summary>
        public static readonly DependencyProperty ReflectedElementProperty =
            DependencyProperty.Register("ReflectedElement", typeof(object), typeof(ReflectionControl),
                new PropertyMetadata(null));

        /// <summary>
        /// Gets or sets the ReflectedElement property. 
        /// </summary>
        public object ReflectedElement
        {
            get { return GetValue(ReflectedElementProperty); }
            set { SetValue(ReflectedElementProperty, value); }
        }

        #endregion

        public ReflectionControl()
        {
            InitializeComponent();

            // initialise a timer at 50ms intervals - and start
            _dispatcherTimer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 0, 0, 50) };
            _dispatcherTimer.Tick += TimerTick;
            _dispatcherTimer.Start();
        }

        private void TimerTick(object sender, EventArgs e)
        {
            // increment phi and update the reflection
            _time += 0.4;
            UpdateReflection();
        }

        /// <summary>
        /// Copies an inverted image of the referenced FrameworkElement
        /// with a 'ripple' effect
        /// </summary>
        private void UpdateReflection()
        {
            FrameworkElement reflectedElement = ReflectedElement as FrameworkElement;

            if (reflectedElement == null)
                return;

            // synchronize the element width
            Width = reflectedElement.ActualWidth;

            // copy the source into a writeable bitmap
            WriteableBitmap writeableBitmapSource = new WriteableBitmap(reflectedElement, null);

            // create a target which is the same width / height as the reflection element
            WriteableBitmap writeableBitmapTarget = new WriteableBitmap((int)ActualWidth, (int)ActualHeight);

            // copy the reflection
            for (int y = 0; y < writeableBitmapTarget.PixelHeight; y++)
            {
                double amplitude = ComputeAmplitude(y, writeableBitmapTarget.PixelHeight);
                double sinusoid = ComputeRipple(y, writeableBitmapTarget.PixelHeight, _time);

                // the offset to the y value index caused by the ripple
                int yOffset = (int)(sinusoid * amplitude);

                // compute the Y position of the line to copy from the source image
                int sourceYLocation = writeableBitmapSource.PixelHeight - 1 -
                    ((y + yOffset) * writeableBitmapSource.PixelHeight) / writeableBitmapTarget.PixelHeight;

                // check that this value is in range
                sourceYLocation = Math.Min(writeableBitmapSource.PixelHeight - 1, Math.Max(0, sourceYLocation));

                // copy the required row
                int sourceIndex = sourceYLocation * writeableBitmapSource.PixelWidth;
                int targetIndex = y * writeableBitmapTarget.PixelWidth;
                for (int i = 0; i < writeableBitmapTarget.PixelWidth; i++)
                {
                    if (writeableBitmapSource.Pixels.Length > sourceIndex + 1)
                        writeableBitmapTarget.Pixels[targetIndex++] = writeableBitmapSource.Pixels[sourceIndex++];
                }
            }

            writeableBitmapTarget.Invalidate();

            LayoutRoot.Source = writeableBitmapTarget;
        }

        /// <summary>
        /// Compute the amplitude of the oscillations at a given Y position
        /// </summary>
        private double ComputeAmplitude(int y, int height)
        {
            // our amplitude range is 1 to 3
            return ((double)y * 2) / height + 1.0;
        }

        /// <summary>
        /// Compute the sinusoid applied to teh image at the given location
        /// </summary>
        private double ComputeRipple(int y, int height, double time)
        {
            // provide a ripple that is the combination of two out of phase sine waves
            double lDPhaseFactor = y / (double)height;
            return Math.Sin(time + lDPhaseFactor * 16) + Math.Sin(time + lDPhaseFactor * 30)
                + Math.Sin(time + lDPhaseFactor * 62);
        }

    }
}
